/*******************************
 * utf-8
 *
 * sphinx http api
 *
 * zhaigy@ucweb.com 2011-04
 *
 * *****************************/
//TODO 搜索功能补全
//TODO 错误异常返回值检查
//TODO 配置文件，记录日志
//TODO 统计、调试功能    <----正在做！！！
//TODO 连接池
//TODO 增加信息、统计、调试页面 

#include <cctype>
#include <cstring>

#include <event.h>
#include <evhttp.h>

#include <iostream>
#include <fstream>
#include <string>
#include <list>
#include <sstream>
#include <algorithm>
#include <functional>

#include "../libsphinxclient/sphinxclient.h"
#include "utils.h"
#include "../json/json/json.h"
#include "../log/log.h"
#include "Config.h"
#include "SphinxQueryData.h"
#include "SearchResult.h"

using namespace std;

/**
 * 记录用户的输入参数
 */
struct GlobalParameter {
	GlobalParameter():  is_daemon(false),
                        host("0.0.0.0"),
	                    port(2046), 
                        proxyed_host("localhost"), 
                        proxyed_port(9312),
                        pid_path_name("./logs/proxy.pid"),
                        log_path_name("./logs/proxy.log") {
	} 
    bool is_daemon;         //是否是后台执行
	string host;		    //ip
	int port;		        //端口
	string proxyed_host;    //代理的sphinx的ip
	int proxyed_port;	    //dialing的sphinx的端口
    string log_path_name;   //日志文件
    string pid_path_name;   //PID文件
};

/**
 * 记录程序运行统计信息
 */
struct Statistic {
    Statistic():query_count(0),
                query_error_count(0),
                start_time(time(NULL)){
    }
	long long query_count;          //查询总次数
	long long query_error_count;	//查询出错次数
	time_t start_time;	        //启动时间
};

/********************全局变量**********************/
//注意：以下两个都是线程级别的，并非真正的全局变量，这里是暂时的
sphinx_client * gp_client;
Json::Writer * gp_json_writer;
Statistic * gp_statistic;
GlobalParameter * gp_parameter;
/**************************************************/
CLog * g_pLog;
#define ERROR_LOG(fmt...)  // g_pLog->error(fmt);fprintf(stderr,fmt)
#define WARN_LOG(fmt...)   // g_pLog->warn(fmt);fprintf(stderr,fmt)
#define INFO_LOG(fmt...)   // g_pLog->info(fmt)
#define DEBUG_LOG(fmt...)  // g_pLog->debug(fmt)
/**************************************************/

/**
 * 实现从输入到查询数据的初步转换
 */
QueryData parserQuery(evkeyvalq & http_query)
{
	QueryData qd;
	//已经处理全部预定义Key
	for (evkeyval * evkv = http_query.tqh_first, *evkvEnd =
	     *http_query.tqh_last; evkv != 0; evkv = evkv->next.tqe_next) {
		DEBUG_LOG("evkv->key:%s, evkv->value:%s.", evkv->key,
			  evkv->value);
		if (strcmp("index", evkv->key) == 0) {
			qd.index = evkv->value;
		} else if (string("q") == evkv->key) {
			qd.q = evkv->value;
		} else if (string("filter") == evkv->key) {
			qd.filters.push_back(evkv->value);
		} else if (string("idrange") == evkv->key) {
			qd.idrange = evkv->value;
		} else if (string("start") == evkv->key) {
			qd.start = atoi(evkv->value);
		} else if (string("rows") == evkv->key) {
			qd.rows = atoi(evkv->value);
		} else if (string("fieldweights") == evkv->key) {
			qd.fieldweights = evkv->value;
		} else if (string("retries") == evkv->key) {
			qd.retries = atoi(evkv->value);
		} else if (string("maxquerytime") == evkv->key) {
			qd.maxquerytime = atoi(evkv->value);
		} else if (string("connecttimeout") == evkv->key) {
			qd.connecttimeout = atoi(evkv->value);
		} else if (string("groupby") == evkv->key) {
			qd.groupby = evkv->value;
		} else if (string("groupdistinct") == evkv->key) {
			qd.groupdistinct = evkv->value;
		} else if (string("matchmode") == evkv->key) {
			qd.matchmode = evkv->value;
		} else if (string("rankingmode") == evkv->key) {
			qd.rankingmode = evkv->value;
		} else if (string("sortmode") == evkv->key) {
			qd.sortmode = evkv->value;
		} else if (string("select") == evkv->key) {
			qd.select = evkv->value;
		} else {
			WARN_LOG("不支持的输入值,key:%s, value:%s",
				 evkv->key, evkv->value);
		}

		if (evkv == evkvEnd) {
			break;
		}
	}
	return qd;
}

/**
 * 调用客户端接口查询
 * TODO 重点！！！
 */
void search(const SphinxQueryData & sqd, SearchResult * sr)
{
	DEBUG_LOG("search...");
	sphinx_client *const client = gp_client;
	sphinx_reset_filters(client);
	sphinx_reset_groupby(client);

	sr->set_client(client);
	//查询之前的准备是否OK
	//bool is_ok=true;

	//filter - 属性值，属性范围，属性范围float,id范围（仅可以设置一个值）
	if (!sqd.filters.empty()) {
		for (list < SphinxQueryData::Filter >::const_iterator it =
		     sqd.filters.begin(); it != sqd.filters.end(); ++it) {
			const SphinxQueryData::Filter & filter = *it;
			if (filter.type == SphinxQueryData::Filter::kValue) {
				if (sphinx_add_filter
				    (client, filter.attr_name.c_str(),
				     filter.values_num, filter.values,
				     filter.exclude)
				    != SPH_TRUE) {
					return;
				}
			} else if (filter.type == SphinxQueryData::Filter::kIntRange) {	//
				if (sphinx_add_filter_range
				    (client, filter.attr_name.c_str(),
				     filter.int_min, filter.int_max,
				     filter.exclude)
				    != SPH_TRUE) {
					return;
				}
			} else if (filter.type ==
				   SphinxQueryData::Filter::kFloatRange) {
				if (sphinx_add_filter_float_range
				    (client, filter.attr_name.c_str(),
				     filter.float_min, filter.float_max,
				     filter.exclude)
				    != SPH_TRUE) {
					return;
				}
			} else {
				throw "Filter 未知的设置类型";
			}
		}
	}
	if (sqd.idrange_min > 0L) {	//id=0 这个在sphinx中是不合法的
		if (sphinx_set_id_range
		    (client, sqd.idrange_min, sqd.idrange_max)
		    != SPH_TRUE) {
			return;
		}
	}
	//第4个参数是否可以设置为rows*10000?
	if (sphinx_set_limits
	    (client, sqd.start, sqd.rows, sqd.start + sqd.rows, 0)
	    != SPH_TRUE) {
		return;
	}
	//fieldweight - 字段数，字段名数组，权重数组（整数）
	if (sqd.fieldweights_num > 0) {
		if (sphinx_set_field_weights(client, sqd.fieldweights_num,
					     sqd.fieldweights_fields,
					     sqd.fieldweights_weights) !=
		    SPH_TRUE) {
			return;
		}
	}

	if (sqd.retries > 0) {
		if (sphinx_set_retries(client, sqd.retries, 100) != SPH_TRUE) {
			//间隔毫秒
			return;
		}
	}
	if (sqd.maxquerytime > 0) {
		if (sphinx_set_max_query_time(client, sqd.maxquerytime) !=
		    SPH_TRUE) {
			//毫秒
			return;
		}
	}
	if (sqd.connecttimeout > 0) {
		if (sphinx_set_connect_timeout
		    (client, float (sqd.connecttimeout) / 1000)
		    != SPH_TRUE) {
			//转换为秒
			return;
		}
	}
	//groupby - groupfun , sort字符串
	if (!sqd.groupby_attr.empty()) {
		if (sphinx_set_groupby(client, sqd.groupby_attr.c_str(),
				       sqd.groupby_func,
				       sqd.groupby_sort.c_str()) != SPH_TRUE) {
			return;
		}
		if (!sqd.groupdistinct.empty()) {
			if (sphinx_set_groupby_distinct
			    (client, sqd.groupdistinct.c_str())
			    != SPH_TRUE) {
				return;
			}
		}
	}

	if (sphinx_set_match_mode(client, sqd.matchmode) != SPH_TRUE
	    || sphinx_set_ranking_mode(client, sqd.rankingmode) != SPH_TRUE
	    || sphinx_set_sort_mode(client, sqd.sortmode,
				    sqd.sortclause.c_str()) != SPH_TRUE) {
		return;
	}

	if (!sqd.select.empty()) {
		if (sphinx_set_select(client, sqd.select.c_str()) != SPH_TRUE) {
			return;
		}
	}
	//sphinx_set_connect_timeout(client,1);
	sphinx_result *res = sphinx_query(client, sqd.q.c_str(),
					  sqd.index.c_str(), NULL);
	sr->set_result(res);
}

/**使用jsoncpp实现的转换*/
string convert_to_json_string(const SearchResult & sr)
{
	Json::Value root;
	//拼输出------------
	root["status_info"]
	    =
	    "0=SEARCHD_OK, 1=SEARCHD_ERROR, 2=SEARCHD_RETRY, 3=SEARCHD_WARNING";
	root["status"] = sr.getStatus();
	if (sr.getStatus() == SEARCHD_ERROR) {
		root["error"] = sr.getError();
	} else if (sr.getStatus() == SEARCHD_RETRY) {
		//无任何说明
	} else {		//ok warning
		if (sr.getStatus() == SEARCHD_WARNING) {
			root["warning"] = sr.getWarning();
		}
		root["total"] = sr.getTotal();
		root["time"] = sr.getTime();
		int matchNumb = sr.getMatchNumb();
		root["matchNumb"] = matchNumb;
		//匹配集合 root["matches"];
		for (int i = 0; i < matchNumb; ++i) {
			Json::Value match;
			match["id"] = (long long)sr.getId(i);
			int attrNumb = sr.getAttrNumb();
			for (int j = 0; j < attrNumb; ++j) {
				if (sr.getAttrType(j) ==
				    (SPH_ATTR_MULTI | SPH_ATTR_INTEGER)) {
					unsigned int *mva =
					    sr.getAttrMultiIntValue(i, j);
					Json::Value array_temp;
					for (unsigned int k = 0; k < mva[0];
					     ++k) {
						array_temp.append(mva[1 + k]);
					}
					match[sr.getAttrName(j)] = array_temp;
				} else if (sr.getAttrType(j) == SPH_ATTR_FLOAT) {
					match[sr.getAttrName(j)] =
					    sr.getAttrFloatValue(i, j);
				} else {	//int
					match[sr.getAttrName(j)] =
					    sr.getAttrIntValue(i, j);
				}
			}
			//这里添加进来
			root["matches"].append(match);
		}

		//for words
		int wordNumb = sr.getWordNumb();
		root["wordNumb"] = wordNumb;
		//words
		for (int i = 0; i < wordNumb; ++i) {
			Json::Value word;
			word["word"] = sr.getWord(i);
			word["hits"] = sr.getHits(i);
			word["docs"] = sr.getDocs(i);
			root["words"].append(word);
		}
	}

	return gp_json_writer->write(root);
}

string make_inner_error_json_string(string e)
{
	Json::Value root;
	root["status_info"]
	    =
	    "0=SEARCHD_OK, 1=SEARCHD_ERROR; 2=SEARCHD_RETRY; 3=SEARCHD_WARNING";
	root["status"] = SEARCHD_ERROR;
	root["error"] = e;
	return gp_json_writer->write(root);
}

/**
 * 返回结果必须是json格式，不能抛出异常
 */
string deal_search_handler(struct evkeyvalq http_query)
{
	try {
		QueryData queryData = parserQuery(http_query);
		if (queryData.q.empty()) {
			ERROR_LOG("查询语句是空的");
            return make_inner_error_json_string("查询语句是空的");
		}
		SphinxQueryData sqd(queryData);
		SearchResult searchResult;
		search(sqd, &searchResult);
		//即便是查询失败，也应该到此，但如发生异常，不会到此处
		return convert_to_json_string(searchResult);
	}
	catch(const char *e) {
		ERROR_LOG("异常:%s", e);
		return make_inner_error_json_string(string("异常：") + e);
	}
	catch(const string & e) {
		ERROR_LOG("异常:%s", e.c_str());
		return make_inner_error_json_string("异常：" + e);
	}
	catch(...) {
		ERROR_LOG("未知的异常");
		return make_inner_error_json_string("未知的异常");
	}
}

/**
 * 处理状态请求
 */
string deal_status_handler(void)
{
	Json::Value root;
	root["status_info"]
	    =
	    "0=SEARCHD_OK, 1=SEARCHD_ERROR; 2=SEARCHD_RETRY; 3=SEARCHD_WARNING";

	sphinx_client *const client = gp_client;
	int row_num;
	int col_num;
	char **status = sphinx_status(client, &row_num, &col_num);
	if (!status) {
		root["status"] = SEARCHD_ERROR;
		root["error"] = sphinx_error(client);
	} else {
		root["status"] = SEARCHD_OK;
		for (int i = row_num * col_num - 1; i >= 0; i -= 2) {
			root[status[i - 1]] = status[i];
		}
	}
	string json = gp_json_writer->write(root);
	sphinx_status_destroy(status, row_num, col_num);
	return json;
}

/**
 * 处理统计请求
 */
string deal_statistic_handler(void)
{
	Json::Value root;
    root["query_count"] = gp_statistic->query_count;
    root["query_error_count"] = gp_statistic->query_error_count;
    root["start_time"] = ctime( &gp_statistic->start_time );
	string json = gp_json_writer->write(root);
	return json;
}

/**
 * 工具方法：读取文本文件内容到内存,文本文件要求是小的，最大不能操作1M，超过仅返回1M内容。
 */
char * read_small_text_file(const char * file_name){
    FILE * f=fopen(file_name,"r");
    if(f==NULL){
        return NULL;
    }
    int size=1024*1024;
    char * text =(char *)malloc(size);
    size_t res=fread(text, 1, size-1, f);
    fclose(f);
    text[res]='\0';
    return text;
}

/**
 * 展示静态页面
 */
void show_web_html(struct evhttp_request * req,struct evbuffer *buf, const char * html_name){
    evhttp_add_header(req->output_headers, "Content-Type",
            "text/html; charset=UTF-8");
    evhttp_add_header(req->output_headers, "Server",
            "my_httpd");
    evhttp_add_header(req->output_headers, "Connection",
            "keep-alive");
    string file("./web/");
    file+=html_name;
    char * text = read_small_text_file(file.c_str());
    if(text==NULL){
        text="读文件异常";
        evbuffer_add_printf(buf, text, NULL);
    }else{
        evbuffer_add_printf(buf, text, NULL);
        free(text);
    }
}


/**处理器,注意,此函数内部如无系统崩溃，不会报错，所有异常应控制在本函数内部*/
void http_handler(struct evhttp_request *req, void *arg)
{
	return;
    try {
		gp_statistic->query_count++;
		struct evbuffer *buf;
		buf = evbuffer_new();
		if (buf == NULL) {
			gp_statistic->query_error_count++;
			//throw "failed to create response buffer";
			WARN_LOG("failed to create response buffer");
			return;
		}
		try {
			const evhttp_uri *uri =
			    evhttp_request_get_evhttp_uri(req);
			string decode_uri(evhttp_request_uri(req));
			DEBUG_LOG("decode_uri:%s", decode_uri.c_str());

			string path = evhttp_uri_get_path(uri);
			DEBUG_LOG("path:%s", path.c_str());

			// 返回给浏览器的信息
			evhttp_add_header(req->output_headers, "Content-Type",
					  "text/plain; charset=UTF-8");
			evhttp_add_header(req->output_headers, "Server",
					  "my_httpd");
			evhttp_add_header(req->output_headers, "Connection",
					  "keep-alive");
			//evhttp_add_header(req->output_headers, "Connection", "close");

			if (path == "/search") {	//处理检索
				struct evkeyvalq http_query;
				evhttp_parse_query(decode_uri.c_str(),
						   &http_query);
				string json = deal_search_handler(http_query);
				DEBUG_LOG("json:%s", json.c_str());
				evbuffer_add_printf(buf, json.c_str(), NULL);
				evhttp_clear_headers(&http_query);
			} else if (path == "/status") {
				DEBUG_LOG("/status");
				//本服务的统计信息 -- 可省略？
				//代理的统计信息
				string json = deal_status_handler();
				evbuffer_add_printf(buf, json.c_str(), NULL);
			} else if (path == "/statistic") {
				string json = deal_statistic_handler();
				evbuffer_add_printf(buf, json.c_str(), NULL);
			} else if (path == "/" || path == "") {
                show_web_html(req,buf,"info.html");
			} else if (path == "/debug") {
                show_web_html(req,buf,"debug.html");
			} else {
				WARN_LOG("未知的路径");
			}
			INFO_LOG("[%s] OK", path.c_str());
		}
		catch(string err) {
			ERROR_LOG(err.c_str());
			gp_statistic->query_error_count++;
		}
		catch(...) {
			ERROR_LOG("未知错误");
			gp_statistic->query_error_count++;
		}
		evhttp_send_reply(req, HTTP_OK, "OK", buf);
		// 内存释放
		evbuffer_free(buf);

		return;
	}
	catch(string err) {
		ERROR_LOG(err.c_str());
	}
	catch(...) {
		ERROR_LOG("未知错误");
	}
	gp_statistic->query_error_count++;
}

/**
 * 加载配置文件
 */
bool load_config_file(){
	try {
		const char * config_file = "./etc/proxy.ini";
		Config configSettings(config_file);

		gp_parameter->is_daemon = configSettings.Read("is_daemon",
							      gp_parameter->is_daemon);
		gp_parameter->host =
		    configSettings.Read("host", gp_parameter->host);
		gp_parameter->port =
		    configSettings.Read("port", gp_parameter->port);
		gp_parameter->proxyed_host =
		    configSettings.Read("proxyed_host",
					gp_parameter->proxyed_host);
		gp_parameter->proxyed_port =
		    configSettings.Read("proxyed_port",
					gp_parameter->proxyed_port);
		gp_parameter->pid_path_name =
		    configSettings.Read("pid_path_name",
					gp_parameter->pid_path_name);
		gp_parameter->log_path_name =
		    configSettings.Read("log_path_name",
					gp_parameter->log_path_name);
		return true;
	}
	catch(Config::File_not_found fnf) {
		fprintf(stderr,"配置文件没有找到\n");
	}
	catch(...) {
		fprintf(stderr,"未知错误\n");
	}
	return false;
}

/**
 * 检查参数配置，失败就退出程序
 */
void check_config_parameter()
{
	cout << "is_daemon: " << gp_parameter->is_daemon << endl;
	cout << "host: " << gp_parameter->host << ":" << gp_parameter->
	    port << endl;
	cout << "proxyed_host: " << gp_parameter->
	    proxyed_host << ":" << gp_parameter->proxyed_port << endl;

	if (gp_parameter->host.empty() || gp_parameter->proxyed_host.empty()) {
		ERROR_LOG("配置不正确");
		exit(1);
	}
}

/**代码移植，非安全退出，待改进*/
void ExecSafeExit(int aiSignal)
{
	struct timeval tv;
	tv.tv_sec = 1;
	tv.tv_usec = 0;
	event_loopexit(&tv);
}

void SetSignalHandle(void)
{
	signal(SIGALRM, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGUSR2, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGUSR1, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);

	signal(SIGINT, ExecSafeExit);
	signal(SIGTERM, ExecSafeExit);
	signal(SIGQUIT, ExecSafeExit);
}

void check_pid_file(){
    if(access(gp_parameter->pid_path_name.c_str(),F_OK)==0){
        ERROR_LOG("PID[%s]文件已经存在",gp_parameter->pid_path_name.c_str());
        fprintf(stderr,"PID[%s]文件已经存在",gp_parameter->pid_path_name.c_str());
        exit(1);
    }
    pid_t pid=getpid();
    FILE * pidFile=fopen(gp_parameter->pid_path_name.c_str(),"w");
    if(pidFile==NULL){
        ERROR_LOG("PID文件创建失败:%s",strerror(errno));
        exit(1);
    }
    fprintf(pidFile,"%d",pid);
    fclose(pidFile);
}

void delet_pid_file(){
    if(remove(gp_parameter->pid_path_name.c_str())<0){
        WARN_LOG("PID[%s]文件删除失败",gp_parameter->pid_path_name.c_str());
    }
}
int main(int argc, char **argv)
{
	gp_parameter = new GlobalParameter;
	//配置文件解析
	if (!load_config_file()) {
		fprintf(stderr,"加载配置文件失败\n");
		return 1;
	}
	//全局的日志
	g_pLog = new CLog(gp_parameter->log_path_name.c_str());
	//g_pLog->set_log_level(CLog::LOG_WARN);
	INFO_LOG("开始");
	//全部参数从配置文件读取,不再接受命令行参数
	check_config_parameter();

	if (gp_parameter->is_daemon) {
		if (fork() == 0 /* CHILD PROCESS */ ) {
			INFO_LOG("Daemon mode start.");
			cout << "Daemon mode start." << endl;
            setsid();
		} else {	//parent
			return 0;
		}
	}
    
    //for pid
    check_pid_file();

	SetSignalHandle();

	gp_statistic = new Statistic;

	struct evhttp *httpd;
	event_init();
	httpd = evhttp_start(gp_parameter->host.c_str(), gp_parameter->port);
	if (httpd == NULL) {
		INFO_LOG("Unable to listen on %s:%d",
			 gp_parameter->host.c_str(), gp_parameter->port);
		exit(1);
	}
	// 设置请求超时时间
	evhttp_set_timeout(httpd, 3);

	gp_client = sphinx_create(SPH_TRUE);
	sphinx_set_server(gp_client, gp_parameter->proxyed_host.c_str(),
			  gp_parameter->proxyed_port);
	sphinx_set_connect_timeout(gp_client, 3.0);
	sphinx_open(gp_client);
	gp_json_writer = new Json::StyledWriter;
	//gp_json_writer=new Json::FastWriter;

	cout << "start, waiting for search..." << endl;
	/* Set a callback for requests to "/specific". */
	/* evhttp_set_cb(httpd, "/specific", another_handler, NULL); */
	/* Set a callback for all other requests. */
	evhttp_set_gencb(httpd, http_handler, NULL);
	event_dispatch();
	/* Not reached in this code as it is now. */
	evhttp_free(httpd);

    delet_pid_file();
	sphinx_close(gp_client);
	cout << "main return!!!" << endl;
	INFO_LOG("结束");

	return 0;
}
